package cn.geminis.crypto.core.x509;

import cn.geminis.core.util.DateUtils;
import cn.geminis.crypto.core.key.PublicKey;
import cn.geminis.crypto.core.util.EncodeUtils;
import cn.geminis.crypto.csp.AbstractCspFactory;
import cn.geminis.crypto.csp.AbstractSigner;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;

import java.io.IOException;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Allen
 */
public class X509Certificate {

    private Certificate bcCertificate;

    private BigInteger serialNumber;
    private String subject;
    private String issuer;
    private LocalDateTime notBefore;
    private LocalDateTime notAfter;
    private PublicKey publicKey;
    private List<Extension> extensions = new ArrayList<>();

    public LocalDateTime getNotBefore() {
        return notBefore;
    }

    public void setNotBefore(LocalDateTime notBefore) {
        this.notBefore = notBefore;
    }

    public LocalDateTime getNotAfter() {
        return notAfter;
    }

    public void setNotAfter(LocalDateTime notAfter) {
        this.notAfter = notAfter;
    }

    public BigInteger getSerialNumber() {
        return serialNumber;
    }

    public void setSerialNumber(BigInteger serialNumber) {
        this.serialNumber = serialNumber;
    }

    public String getSubject() {
        return subject.toString();
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public List<Extension> getExtensions() {
        return extensions;
    }

    public void setExtensions(List<Extension> extensions) {
        this.extensions = extensions;
    }

    public Certificate getBcCertificate() {
        return bcCertificate;
    }

    public String getSubject(String oid) {
        X500Name subject = this.bcCertificate.getSubject();
        RDN[] values = subject.getRDNs(new ASN1ObjectIdentifier(oid));
        return values.length > 0 ? values[0].getTypesAndValues()[0].getValue().toString() : null;
    }

    public PublicKey getPublicKey() {
        return publicKey;
    }

    public void setPublicKey(PublicKey publicKey) {
        this.publicKey = publicKey;
    }

    public String getIssuer() {
        return issuer;
    }

    public byte[] getEncode() {
        return EncodeUtils.toDER(this.bcCertificate);
    }

    public String getFormat() {
        return EncodeUtils.toPEM(new X509CertificateHolder(this.bcCertificate));
    }

    public void generate(AbstractSigner signer) {
        this.generate(signer, null);
    }

    public void generate(AbstractSigner signer, X509Certificate issuer) {
        X500Name subjectName = new X500Name(new Utf8Style(), this.subject);
        X500Name issuerSubjectName = issuer == null ? subjectName : issuer.bcCertificate.getSubject();

        X509v3CertificateBuilder builder = new X509v3CertificateBuilder(
                issuerSubjectName,
                this.serialNumber,
                DateUtils.toDate(this.notBefore),
                DateUtils.toDate(this.notAfter),
                subjectName,
                this.publicKey.getSubjectPublicKeyInfo());

        this.extensions.forEach(extension -> {
            try {
                builder.addExtension(extension);
            } catch (CertIOException e) {
                throw new RuntimeException("设置证书扩展项错误", e);
            }
        });

        signer.init(true, null);
        X509CertificateHolder holder = builder.build(signer.createContentSigner());
        this.bcCertificate = holder.toASN1Structure();
    }

    public boolean verify(X509Certificate issuer) {
        try (var signer = AbstractCspFactory.getSigner(this.bcCertificate.getSignatureAlgorithm().getAlgorithm().getId())) {
            byte[] data;
            try {
                data = this.bcCertificate.getTBSCertificate().getEncoded();
            } catch (IOException e) {
                throw new RuntimeException("TBS证书编码错误", e);
            }
            byte[] signature = this.bcCertificate.getSignature().getBytes();

            return signer.verifySignature(data, signature, issuer.getPublicKey());
        } catch (IOException e) {
            throw new RuntimeException("释放签名器错误", e);
        }
    }

    public boolean verify(byte[] data, byte[] signature) throws Exception {
        AbstractSigner signer = AbstractCspFactory.getSigner(this.publicKey.getAlgorithm());
        return signer.verifySignature(data, signature, this.getPublicKey());
    }

    public X509Certificate() {

    }

    public X509Certificate(byte[] data) {
        this(Certificate.getInstance(EncodeUtils.fromDERorPEM(data)));
    }

    public X509Certificate(Certificate cert) {
        this.bcCertificate = cert;
        this.notAfter = DateUtils.toLocalDateTime(this.bcCertificate.getEndDate().getDate());
        this.notBefore = DateUtils.toLocalDateTime(this.bcCertificate.getStartDate().getDate());
        this.serialNumber = this.bcCertificate.getSerialNumber().getValue();
        this.subject = this.bcCertificate.getSubject().toString();
        this.issuer = this.bcCertificate.getIssuer().toString();
        this.publicKey = new PublicKey(this.bcCertificate.getSubjectPublicKeyInfo());
    }
}
